package edu.colorado;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import edu.colorado.core.Algorithm;
import edu.colorado.core.Board;
import edu.colorado.core.Game.Level;
import edu.colorado.core.Game;
import edu.colorado.core.Game.Mode;
import edu.colorado.core.Game.State;
import edu.colorado.core.HighScore;
import edu.colorado.core.HighScoreList;
import android.animation.AnimatorSet;
import android.animation.ArgbEvaluator;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.OvalShape;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.animation.BounceInterpolator;
import android.widget.EditText;
import android.widget.LinearLayout.LayoutParams;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;

public class GameActivity extends Activity implements OnClickListener {
    public static final String LEVEL = "level";
    public static final String MODE = "mode";

    public static final String SAVED_GAME = "game";
    
    public static final String SAVED_TOURNAMENT = "tournament";

    public static final String SAVED_SCORES = "scores";

    private static final int RADIUS = 30;
    private static final int PADDING = 3;

    private Game game;

    private HighScoreList scores;

    private BoardView boardView;

    private Button saveButton, startOverButton;

    private TextView scoreDisplay;

    private int rows;
    private int cols;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Level level = (Level) getIntent().getExtras().get(LEVEL);
        Mode mode = (Mode) getIntent().getExtras().get(MODE);
        if ((getIntent().getExtras().get(SAVED_GAME) != null) ||
                (getIntent().getExtras().get(SAVED_TOURNAMENT) != null)) {
            try {
                game = loadGame(mode);
            } catch (Exception e) {
                Log.e("Connect4", "log game", e);
                game = new Game(Level.Easy, Mode.Game);
            }
        } else {
            game = new Game(level, mode);
        }

        try {
            scores = loadScores();
        } catch (Exception e) {
            Log.e("Connect4", "log scores", e);
        }

        cols = game.getBoard().getCols();
        rows = game.getBoard().getRows();

        LinearLayout main = new LinearLayout(this);
        main.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT,
                LayoutParams.FILL_PARENT));
        main.setOrientation(LinearLayout.HORIZONTAL);
        main.setGravity(Gravity.CENTER);
        main.addView(createPlayArea());

        LinearLayout layout = new LinearLayout(this);
        layout.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, 
                LayoutParams.FILL_PARENT));
        layout.setOrientation(LinearLayout.VERTICAL);
        layout.addView(createLabel());
        layout.addView(main);
        
        updateScoreDisplay("--");

        setContentView(layout);
    }

    private LinearLayout createPlayArea()
    {
        final int width = cols * (2 * (RADIUS + PADDING));
        final int height = rows * (2 * (RADIUS + PADDING));

        LinearLayout playArea = new LinearLayout(this);
        playArea.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, 
                LayoutParams.FILL_PARENT));
        playArea.setOrientation(LinearLayout.VERTICAL);
        playArea.setGravity(Gravity.CENTER);
        playArea.addView(createScoreDisplay());
        playArea.addView(createDropButtons(cols));
        playArea.addView(createBoard(width, height));
        playArea.addView(createBottomButtons());

        return playArea;
    }

    private TextView createScoreDisplay() {
        scoreDisplay = new TextView(this);
        scoreDisplay.setText("Score: --");
        scoreDisplay.setTextSize(TypedValue.COMPLEX_UNIT_SP, 15);
        scoreDisplay.setGravity(Gravity.CENTER);
        scoreDisplay.setPadding(0, 0, 0, 5);
        return scoreDisplay;
    }
    
    private void updateScoreDisplay() {
        updateScoreDisplay(Integer.toString(game.getScore()));
    }

    private void updateScoreDisplay(String score) {
        scoreDisplay.setText("Score: " + score);
    }

    private ShapeHolder createBall(int row, int col, int color) {        
        // create ball
        OvalShape circle = new OvalShape();
        circle.resize(2 * RADIUS, 2 * RADIUS);
        ShapeDrawable drawable = new ShapeDrawable(circle);
        ShapeHolder sh = new ShapeHolder(drawable);
        sh.setX((RADIUS + PADDING) * (2 * col) + PADDING);
        sh.setY((RADIUS + PADDING) * (2 * row) + PADDING);
        sh.setCol(col);
        sh.setRow(row);

        Paint paint = drawable.getPaint();
        paint.setColor(color);
        sh.setPaint(paint);

        // create animation
        ValueAnimator dropAnim = ObjectAnimator.ofFloat(sh, "y", 0, sh.getY());
        dropAnim.setDuration(2000);
        dropAnim.setInterpolator(new BounceInterpolator());

        AnimatorSet animatorSet = new AnimatorSet();
        animatorSet.play(dropAnim);
        animatorSet.start();

        return sh;
    }

    @Override
    public void onClick(View v) {
        if (v == saveButton) {
            try {
                saveGame();
            } catch (IOException e) {
                Log.e("Connect4", "save game", e);
            }
            startActivity(new Intent(this, Connect4Activity.class));
        } else if (v == startOverButton) {
            if (game.getMode() == Mode.Game) {
                game.startOver();
                boardView.setPath(null);
                boardView.invalidate();
                boardView.clearBalls();
                updateScoreDisplay("--");
            } else {
                Level nextLevel = game.getLevel() == Level.Easy ? Level.Medium :
                    Level.Hard;
                Intent next = new Intent(this, GameActivity.class);
                next.putExtra(GameActivity.MODE, Game.Mode.Tournament);
                next.putExtra(GameActivity.LEVEL, nextLevel);
                startActivity(next);
            }
        } else {
            int col = v.getId();
            if (game.getBoard().canDrop(col)) {
                int[] human = game.makeMove(Algorithm.HUMAN, col);
                final int[] ai;
                if (human != null) {
                    int r = rows - 1 - human[0], c = human[1];
                    boardView.addBall(createBall(r, c, BoardView.YELLOW));
                    ai = game.makeMove(Algorithm.DEVICE, -1);
                } else {
                    ai = null;
                }

                new Handler().postDelayed(new Runnable() {
                    public void run() {
                        if (ai != null) {
                            int r = rows - 1 - ai[0], c = ai[1];
                            boardView.addBall(createBall(r, c, BoardView.RED));
                        }
                        
                        if (game.getState() == State.Win &&
                                game.getLevel() != Level.Hard) {
                            startOverButton.setEnabled(true);
                        }
                        
                        updateScoreDisplay();
                        boardView.invalidate();

                        if (game.getState() == State.Win ||
                                game.getState() == State.Loss) {
                            int player = game.getState() == State.Win ? 
                                    Algorithm.HUMAN : Algorithm.DEVICE;
                            boardView.setPath(game.getFour(player));
                            new Handler().postDelayed(new Runnable() {
                                public void run() {
                                    if (scores.isHighScore(game.getScore())) {
                                        getHighScoreInput();
                                    }
                                    boardView.invalidate();
                                }
                            }, 3000);
                        }
                    }
                }, 3000);
            }
        }
    }

    private void getHighScoreInput() {
        final EditText input = new EditText(this);        
        new AlertDialog.Builder(this)
        .setTitle("New High Score!")
        .setMessage("Please enter your name")
        .setView(input)
        .setPositiveButton("Ok", new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int whichButton) {
                String name = input.getText().toString();
                int score = game.getScore();
                scores.addScore(new HighScore(name, score));
                try {
                    saveScores();
                } catch (IOException e) {
                    Log.e("Connect4", "log score save", e);
                }
            }
        }).setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int whichButton) {
                // Do nothing.
            }
        }).show();
    }

    private TextView createLabel() {
        TextView label = new TextView(this);
        if (game.getMode() == Mode.Game) {
            label.setText("Connect4: " + game.getLevel());
        } else if (game.getMode() == Mode.Tournament) {
            int round = game.getLevel() == Level.Easy ? 1 : 
                game.getLevel() == Level.Medium ? 2 : 3;
            label.setText("Connect4: Tournament Round " + round);
        }
        label.setTextSize(TypedValue.COMPLEX_UNIT_SP, 25);
        label.setGravity(Gravity.CENTER);
        label.setPadding(0, 0, 0, 15);
        return label;
    }

    private LinearLayout createDropButtons(int cols) {
        LinearLayout dropLayout = new LinearLayout(this);
        dropLayout.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT,
                LayoutParams.WRAP_CONTENT));
        dropLayout.setOrientation(LinearLayout.HORIZONTAL);
        dropLayout.setGravity(Gravity.CENTER);

        for (int c = 0; c < cols; c++) {
            Button col = new Button(this);
            col.setId(c);
            col.setOnClickListener(this);
            dropLayout.addView(col);
        }

        return dropLayout;
    }

    private LinearLayout createBoard(int width, int height) {
        LinearLayout boardLayout = new LinearLayout(this);
        boardLayout.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, 
                LayoutParams.WRAP_CONTENT));
        boardLayout.setOrientation(LinearLayout.HORIZONTAL);
        boardLayout.setGravity(Gravity.CENTER);

        boardView = new BoardView(this);
        boardView.setLayoutParams(new LayoutParams(width, height));
        boardLayout.addView(boardView);

        return boardLayout;
    }

    private LinearLayout createBottomButtons() {
        LinearLayout bottomLayout = new LinearLayout(this);
        LayoutParams params = new LayoutParams(LayoutParams.FILL_PARENT,
                LayoutParams.WRAP_CONTENT);
        params.setMargins(0, 10, 0, 0);
        bottomLayout.setLayoutParams(params);
        bottomLayout.setOrientation(LinearLayout.HORIZONTAL);
        bottomLayout.setGravity(Gravity.CENTER);

        saveButton = new Button(this);
        saveButton.setText("Save");
        saveButton.setOnClickListener(this);
        bottomLayout.addView(saveButton);
        
        startOverButton = new Button(this);
        startOverButton.setOnClickListener(this);
        bottomLayout.addView(startOverButton);
        
        if (game.getMode() == Mode.Game) {
            startOverButton.setText("Start Over");
        } else if (game.getMode() == Mode.Tournament) {
            startOverButton.setText("Next Round");
            startOverButton.setEnabled(false);
        }

        return bottomLayout;
    }

    private void saveGame() throws IOException {
        String file = game.getMode() == Mode.Game ? SAVED_GAME : 
            SAVED_TOURNAMENT;
        serialize(file, game);
    }

    private void saveScores() throws IOException {
        serialize(SAVED_SCORES, scores);
    }

    private Game loadGame(Mode mode) throws IOException, ClassNotFoundException {
        String file = mode == Mode.Game ? SAVED_GAME : SAVED_TOURNAMENT;
        return (Game) deserialize(file);
    }

    private HighScoreList loadScores() throws IOException, 
            ClassNotFoundException {
        try {
            return (HighScoreList) deserialize(SAVED_SCORES);
        } catch (Exception e) {
            HighScoreList scores = new HighScoreList();
            serialize(SAVED_SCORES, scores);
            return scores;
        }
    }

    private void serialize(String path, Serializable obj) throws IOException {
        ObjectOutputStream out = null;
        try {
            out = new ObjectOutputStream(openFileOutput(path, 
                    Context.MODE_PRIVATE));
            out.writeObject(obj);
        } finally {
            if (out != null) {
                out.close();
            }
        }
    }

    private Object deserialize(String path) throws IOException, 
            ClassNotFoundException {
        Object obj = null;
        ObjectInputStream in = null;
        try {
            in = new ObjectInputStream(openFileInput(path));
            obj = in.readObject();
        } finally {
            if (in != null) {
                in.close();
            }
        }
        return obj;
    }

    static class BoardView extends View {
        static final int LIGHT_GREEN1 = Color.rgb(60, 180, 60);
        static final int LIGHT_GREEN2 = Color.rgb(45, 200, 85);
        static final int DARK_GREEN = Color.rgb(10, 130, 35);
        static final int RED = Color.rgb(250, 120, 105);
        static final int YELLOW = Color.rgb(250, 245, 75);

        private Set<String> path;
        private Map<String, ShapeHolder> balls;

        public BoardView(Context context) {
            super(context);
            path = new HashSet<String>();
            balls = new HashMap<String, ShapeHolder>();

            ValueAnimator colorAnim = ObjectAnimator.ofInt(this, 
                    "backgroundColor", LIGHT_GREEN1, LIGHT_GREEN2);
            colorAnim.setDuration(3000);
            colorAnim.setEvaluator(new ArgbEvaluator());
            colorAnim.setRepeatCount(ValueAnimator.INFINITE);
            colorAnim.setRepeatMode(ValueAnimator.REVERSE);
            colorAnim.start();
        }

        private GameActivity getGameActivity() {
            return (GameActivity) getContext();
        }

        private Game getGame() {
            return getGameActivity().game;
        }

        private Board getBoard() {
            return getGame().getBoard();
        }

        void setPath(int[][] path) {
            this.path = new HashSet<String>();
            if (path != null && path.length == 2 && 
                    path[0].length == 4 && path[1].length == 4) {
                for (int i = 0; i < 4; i++) {
                    this.path.add(path[0][i] + "," + path[1][i]);
                }
            }
        }

        void addBall(ShapeHolder ball) {
            balls.put(ball.getRow() + "," + ball.getCol(), ball);
        }

        ShapeHolder getBall(int row, int col) {
            return balls.get(row + "," + col);
        }

        void clearBalls() {
            balls.clear();
        }

        @Override
        protected void onDraw(Canvas canvas) {
            //canvas.drawColor(LIGHT_GREEN);

            Board board = getBoard();
            int rows = board.getRows();
            int cols = board.getCols();

            Paint paint = new Paint();
            paint.setAntiAlias(true);

            for (int r = 0, y = RADIUS + PADDING; r < rows; r++) {
                for (int c = 0, x = RADIUS + PADDING; c < cols; c++) {
                    paint.setColor(DARK_GREEN);
                    canvas.drawCircle(x, y, RADIUS, paint);
                    x += 2 * (RADIUS + PADDING);
                }
                y += 2 * (RADIUS + PADDING);
            }

            for (ShapeHolder ball : balls.values()) {
                canvas.save();
                canvas.translate(ball.getX(), ball.getY());
                ball.getShape().draw(canvas);
                canvas.restore();
            }

            if (balls.isEmpty() || !path.isEmpty()) {
                boolean empty = balls.isEmpty();
                GameActivity ga = getGameActivity();
                for (int r = 0, y = RADIUS + PADDING; r < rows; r++) {
                    for (int c = 0, x = RADIUS + PADDING; c < cols; c++) {
                        int delta = 0;
                        if (path.contains((rows - r - 1) + "," + c)) {
                            paint.setColor(Color.BLUE);
                            canvas.drawCircle(x, y, RADIUS, paint);
                            delta = -3;
                        }

                        if (board.getPiece(c, rows - r - 1) == Algorithm.HUMAN) {
                            paint.setColor(YELLOW);
                            if (empty) {
                                paint.setColor(DARK_GREEN);
                                addBall(ga.createBall(r, c, YELLOW));
                            }
                        } else if (board.getPiece(c, rows - r - 1) == Algorithm.DEVICE) {
                            paint.setColor(RED);
                            if (empty) {
                                paint.setColor(DARK_GREEN);
                                addBall(ga.createBall(r, c, RED));
                            }
                        } else {
                            paint.setColor(DARK_GREEN);
                        }

                        canvas.drawCircle(x,  y, RADIUS + delta, paint);
                        x += 2 * (RADIUS + PADDING);
                    }
                    y += 2 * (RADIUS + PADDING);
                }
            }
        }
    }
}